package org.chen.spring.aop.processor;

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import org.chen.spring.ChenApplicationContext;
import org.chen.spring.aop.JoinPoint;
import org.chen.spring.aop.ProceedingJoinPoint;
import org.chen.spring.aop.annotation.After;
import org.chen.spring.aop.annotation.AfterThrowing;
import org.chen.spring.aop.annotation.Around;
import org.chen.spring.aop.annotation.Before;
import org.chen.spring.aop.factory.AspectFactory;
import org.chen.spring.core.annotation.Autowired;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;


/**
 * @author Chen
 * @version 1.0
 **/
public class AopInterceptor implements MethodInterceptor {

    /**
     * @param obj         表示要进行增强的对象
     * @param method      表示拦截的方法
     * @param objects     数组表示参数列表，基本数据类型需要传入其包装类型，如int-->Integer、long-Long、double-->Double
     * @param methodProxy 表示对方法的代理，invokeSuper方法表示对被代理对象方法的调用
     * @return 执行结果
     * @throws Exception 异常
     */
    @Override
    public Object intercept(Object obj, Method method, Object[] objects, MethodProxy methodProxy) throws Exception {
        JoinPoint joinPoint = new JoinPoint(method, objects);
        aspectInvoke(Before.class.getSimpleName(), joinPoint);
        for (Field field : obj.getClass().getSuperclass().getDeclaredFields()) {
            //如果带有Autowired注解，则注入需要的依赖
            if (field.isAnnotationPresent(Autowired.class)) {
                field.setAccessible(true);
                try {
                    field.set(obj, ChenApplicationContext.getBeanFactory().getBean(field.getName()));
                } catch (IllegalAccessException e) {
                    throw new RuntimeException(e);
                }
            }
        }
        Object result;
        result = around(new ProceedingJoinPoint(method, objects, obj, methodProxy));
        aspectInvoke(After.class.getSimpleName(), joinPoint);
        return result;
    }

    /**
     * 环绕通知
     *
     * @param pjr 切面通知类
     */
    private Object around(ProceedingJoinPoint pjr) throws Exception {
        //获取所有切面
        ConcurrentHashMap<Annotation, Map<String, Method>> aspectMap = AspectFactory.getAspectMap();
        //获取所有切面的key注解类型
        ConcurrentHashMap.KeySetView<Annotation, Map<String, Method>> annotations = aspectMap.keySet();
        //遍历所有切面key
        boolean isAround = false;
        for (Annotation annotation : annotations) {
            //如果是Before注解
            if (annotation.annotationType().getSimpleName().equals(Around.class.getSimpleName())) {
                isAround = true;
                Map<String, Method> stringMethodMap = aspectMap.get(annotation);
                for (Annotation methodAnnotation : pjr.getMethod().getAnnotations()) {
                    Method beforeMethod = stringMethodMap.get(methodAnnotation.annotationType().getSimpleName());
                    if (beforeMethod != null) {
                        try {
                            boolean flag = beforeMethod.getParameters()[0] != null;
                            // 注意这里是调用invokeSuper而不是invoke，否则死循环;
                            // methodProxy.invokeSuper执行的是原始类的方法;
                            // method.invoke执行的是子类的方法;
                            return beforeMethod.invoke(ChenApplicationContext.getBeanFactory().getBean(beforeMethod.getDeclaringClass().getSimpleName()), flag ? pjr : null);
                        } catch (Exception e) {
                            aspectInvoke(AfterThrowing.class.getSimpleName(), pjr, e);
                            e.printStackTrace();
                            throw e;
                        }
                    }
                }
            }
        }
        if(isAround) {
            // 注意这里是调用invokeSuper而不是invoke，否则死循环;
            // methodProxy.invokeSuper执行的是原始类的方法;
            // method.invoke执行的是子类的方法;
            return pjr.proceed();

        }
        return null;
    }

    /**
     * 切面调用
     */
    private void aspectInvoke(String aspectAnnotationName, JoinPoint joinPoint, Exception... exceptions) throws Exception {
        //获取所有切面
        ConcurrentHashMap<Annotation, Map<String, Method>> aspectMap = AspectFactory.getAspectMap();
        //获取所有切面的key注解类型
        ConcurrentHashMap.KeySetView<Annotation, Map<String, Method>> annotations = aspectMap.keySet();
        //遍历所有切面key
        for (Annotation annotation : annotations) {
            //如果是Before注解
            if (annotation.annotationType().getSimpleName().equals(aspectAnnotationName)) {
                Map<String, Method> stringMethodMap = aspectMap.get(annotation);
                for (Annotation methodAnnotation : joinPoint.getMethod().getAnnotations()) {
                    Method beforeMethod = stringMethodMap.get(methodAnnotation.annotationType().getSimpleName());
                    if (beforeMethod != null) {
                        try {
                            Object[] args = new Object[beforeMethod.getParameterCount()];
                            Parameter[] parameters = beforeMethod.getParameters();
                            for (int i = 0, parametersLength = parameters.length; i < parametersLength; i++) {
                                Parameter parameter = parameters[i];
                                if (parameter.getType().getSimpleName().equals(JoinPoint.class.getSimpleName())) {
                                    args[i] = joinPoint;
                                    continue;
                                }
                                if (parameter.getType().getSimpleName().equals(Exception.class.getSimpleName())) {
                                    args[i] = exceptions[0];
                                    continue;
                                }
                                throw new RuntimeException("切面方法参数只能是JoinPoint或Exception");
                            }
                            beforeMethod.invoke(ChenApplicationContext.
                                    getBeanFactory().getBean(beforeMethod.getDeclaringClass().getSimpleName()), args);
                        } catch (Exception e) {
                            aspectInvoke(AfterThrowing.class.getSimpleName(), joinPoint, e);
                            e.printStackTrace();
                            throw e;
                        }
                    }
                }
            }
        }
    }

}